/* 
 * Copyright (c) 2004-2005 Massachusetts Institute of Technology. This code was
 * developed as part of the Haystack (http://haystack.lcs.mit.edu/) research 
 * project at MIT. Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including without 
 * limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions: 
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. 
 */
/*
 * Created on Sep 6, 2004
 *
 */
package jiggle.relo;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import jiggle.AbstractForceLaw;
import jiggle.Cell;
import jiggle.ForceLaw;
import jiggle.Graph;
import jiggle.Vertex;

/**
 * This ForceLaw applies an attraction force to children of subgraph an a 
 * reuplsion force to other noes (especially those insie the subgraph)
 * 
 * Scenario 3 tests this law, we might want to modify to amplify forces on the 
 * outer edges, actually apply forces only to the ones on the outer edges
 *  
 * @author vineet
 *  
 */
public class SubgraphForceLaw extends ForceLaw {

    protected double preferredBoundaryLength;
    protected AbstractForceLaw attractionLaw;
    protected AbstractForceLaw repulsionLaw;

    public SubgraphForceLaw(Graph g, double k, AbstractForceLaw attrLaw, AbstractForceLaw repulseLaw) {
        super(g);
        preferredBoundaryLength = k;
        attractionLaw = attrLaw;
        repulsionLaw = repulseLaw;

        // init connected map
        int nv = graph.getNumberOfVertices();
        for (int i = 0; i < nv; i++) {
            if (!(graph.vertices.get(i) instanceof Subgraph)) {
                continue;
            }
            ((Subgraph)graph.vertices.get(i)).initConnectedMap();
        }
    }

    @Override
    public void apply(double[][] negativeGradient) {
        int nv = graph.getNumberOfVertices();
        int nd = graph.getDimensions();
        //int ne = graph.numberOfEdges;

	    double oldPreferredEdgeLength = repulsionLaw.preferredEdgeLength;
        repulsionLaw.preferredEdgeLength = preferredBoundaryLength;
        
        for (int i = 0; i < nv; i++) {
            if (!(graph.vertices.get(i) instanceof Subgraph)) {
                continue;
            }
            applyForSubgraph(negativeGradient, (Subgraph) graph.vertices.get(i));
            //applyPackingForSubgraph(negativeGradient, (Subgraph) graph.vertices[i]);
        }

        // since sub-graph coords are being moved directly
        // note: to remove graph-graph interactions we do this at the end
        for (int i = 0; i < nv; i++) {
            if (!(graph.vertices.get(i) instanceof Subgraph)) {
                continue;
            }
            for (int j = 0; j < nd; j++) {
                negativeGradient[i][j] = 0;
            }
        }


        repulsionLaw.preferredEdgeLength = oldPreferredEdgeLength;
        
    }

    /**
     * @param negativeGradient
     * @param sg
     */
    private void applyForSubgraph(double[][] negativeGradient, Subgraph sg) {
        int sgnv = sg.getNumberOfVertices();
        //int nv = graph.numberOfVertices();

        // first re-position subgraphs
        // TODO there is a bug, remove need for this
        sg.setCoords(sg.getBorderCoords());
        

        // we are basically modelling extra relations and using springLaw to implement them
        
        /*
         * We want to take the center and do two steps:
         * 1] apply an attraction to all nodes inside the sub-graph
         * 2] apply a repulsion to all nodes outside the sub-graph
         * 
         * The above does not work (for some reason) - need to figure out
         * What does work, and has been implemented below, is a simple 
         * repulsion from all nodes in the subgraph to all nodes not in
         * the subgraph
         */
        
        // do attraction to graph for children
        for (int i=0; i< sgnv; i++) {
            Cell childCell = sg.vertices.get(i);
            attractionLaw.applyForCells(negativeGradient, sg, childCell);
        }
        

        // do repelling from graph for non-children
        Set<Cell> ignoreForRepulse = new HashSet<Cell>();
        sg.getChildrenRecursively(ignoreForRepulse);
        ignoreForRepulse.add(sg);
        
        /*
        // add an extra amout of repulsion from each child to all non-chilren
        for (int i = 0; i < nv; i++) {
            Cell graphVertex = graph.vertices[i];
            if (ignoreForRepulse.contains(graphVertex)) {
                continue;
            }

            //System.err.print(".");
            //int nd  = sg.getDimensions();
            //double[] radiusSegment = Cell.getSegment(sg.getCoords(), graphVertex.getCoords());
            //repulsionLaw.preferredEdgeLength = Cell.radius(nd, sg.getBorderSize(), radiusSegment);
            //repulsionLaw.applyForCells(negativeGradient, sg, graphVertex);
            for (int j=0; j< sgnv; j++) {
                Cell childCell = sg.vertices[j];
                repulsionLaw.applyForCells(negativeGradient, childCell, graphVertex);
            }
        }
        */
        
        // do repulsion using the whole subgraph as a cell
        Cell fakeSG = new Vertex(graph);
        fakeSG.setCoords(sg.getBorderCoords());
        fakeSG.setSize(sg.getBorderSize());
        int n = graph.getNumberOfVertices();
        for (int i = 0; i < n; i++) {
            Cell v = graph.vertices.get(i);
            if (ignoreForRepulse.contains(v)) {
                continue;
            }

            repulsionLaw.applyForCells(negativeGradient, v, fakeSG);
        }
        
    }

    @SuppressWarnings("unused")
	private void applyPackingForSubgraph(double[][] negativeGradient, Subgraph sg) {
        // TODO figure out why this line is needed
        if (sg.arrCC.length == 0) {
            return;
        }

        // let's just apply forces to the center of the larges connected components
        
        // get centroid of largestCC
		int d = sg.getDimensions ();
		//int sgnv = sg.numberOfVertices();
		Cell mainCell = getCellForCC(d, sg.arrCC[sg.largestCC]);

		for (int i=0; i<sg.arrCC.length; i++) {
		    if (i==sg.largestCC) continue;
		    
        	// get force to centroid
			Cell neighbourCell = getCellForCC(d, sg.arrCC[i]);
			
        	// apply force to all children
			//attractionLaw.applyForCells(negativeGradient, mainCell, neighbourCell);
			Cell from = mainCell;
			Cell to = neighbourCell;
			
		    double fromCoords[] = from.getCoords();
	        double toCoords[] = to.getCoords();
		    
	        double w = attractionLaw.pairwiseWeights(from, to);
	        w = Math.min(w, cap / Cell.getDistance(from, to));
	        w = -Math.min(-w, cap / Cell.getDistance(from, to));
	        for (int j = 0; j < d; j++) {								// per dimension
	            double force = (toCoords[j] - fromCoords[j]) * w;
	            Iterator neighbourCCIt = sg.arrCC[i].iterator();
	            while (neighbourCCIt.hasNext()) {
	                Cell neighbourCCChild = (Cell) neighbourCCIt.next();
	                if (Double.isNaN(force)) {
	                    System.err.println("**** NaN packing force! ****");
	                    return;
	                }
	                negativeGradient[neighbourCCChild.gradientNdx][j] -= force/1000000 /** from.getWeight()*/;
	            }
	        }
		}
		
    }


    private Cell getCellForCC(int nd, Set connectedComponentChildren) {
		Cell ccCell = new Vertex(graph);
		
        // we are modifying the cell's boundaries directly!!
        double minCoord[] = ccCell.getMin();
        double maxCoord[] = ccCell.getMax();

        double[] vertexCoords;
        double[] vertexSize;

        for (int j = 0; j < nd; j++) {
            minCoord[j] = Double.MAX_VALUE;
            maxCoord[j] = Double.MIN_VALUE;
        }

        Iterator ccChildrenIt = connectedComponentChildren.iterator();
        while (ccChildrenIt.hasNext()) {
            Cell child = (Cell) ccChildrenIt.next();
            vertexCoords = child.getCoords();
            vertexSize = child.getSize();
            for (int j = 0; j < nd; j++) {
                minCoord[j] = Math.min(minCoord[j], vertexCoords[j] - (vertexSize[j] / 2));
                maxCoord[j] = Math.max(maxCoord[j], vertexCoords[j] + (vertexSize[j] / 2));
            }
        }

        ccCell.recomputeSize();
        ccCell.recomputeCoords();

		return ccCell;
    }

	

}